package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.core.search.*;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.order.OrderDetail;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.client.OrderInfoFeignClient;
import com.atguigu.tingshu.search.factory.ScheduleTaskThreadPoolFactory;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.runnable.RebuildBloomFilterRunnable;
import jakarta.annotation.PostConstruct;
import org.codingyz.cache.service.CacheOpsService;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import com.atguigu.tingshu.model.search.SuggestIndex;

import java.io.IOException;
import java.util.Date;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.service.ItemService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class ItemServiceImpl implements ItemService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private AlbumIndexRepository albumIndexRepository;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RBloomFilter rBloomFilter;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ItemServiceImpl itemServiceImpl;


    private static final Map<Long, Map<String, Object>> localCacheMap = new HashMap<>();

    private final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;


    @SneakyThrows
    @Override
    public List<Map<String, Object>> getChannel(Long category1Id) {
        // 1.根据一级分类id查询该分类下7个置顶的3级分类
        Result<List<BaseCategory3>> category3ListResult = albumInfoFeignClient.getBaseCategory3ListByCategory1Id(category1Id);
        List<BaseCategory3> listResultData = category3ListResult.getData();
        if (CollectionUtils.isEmpty(listResultData))
            throw new GuiguException(201, "远程调用分类信息失败");

        // 2.过滤获取三级分类id --> FieldValue类型(Es在查询时 需要用到这个类型)
        List<FieldValue> c3FieldListValues = listResultData.stream().map(data -> FieldValue.of(data.getId())).toList();

        // 3.将三级分类的集合转为一个map
        Map<Long, BaseCategory3> c3IdAndBaseCategory3 = listResultData.stream().collect(Collectors.toMap(BaseCategory3::getId, v -> v));

        // 4.构建DSL语句
        SearchRequest searchRequest = buildChannelDsl(c3FieldListValues);
        log.info("查询DSL语句为{}", searchRequest);
        // 5.查询Es
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        // 6.解析Es中的数据并返回
        List<Map<String, Object>> channelData = parseChannelData(response, c3IdAndBaseCategory3);
        return channelData;
    }

    /**
     * 构建DSL语句
     *
     * @param c3FieldListValues
     * @return
     */
    private SearchRequest buildChannelDsl(List<FieldValue> c3FieldListValues) {
        // 1.构建SearchRequestBuilder对象
        SearchRequest.Builder builder = new SearchRequest.Builder();
        // 2.构建查询语句
        builder.index("albuminfo")
                .query(q -> q.terms(tq -> tq.field("category3Id")
                        .terms(tqf -> tqf.value(c3FieldListValues))))
                .aggregations("AggByCategory3Id", a -> a
                        .terms(ta -> ta
                                .field("category3Id")
                                .size(c3FieldListValues.size()))
                        .aggregations("top6HotScoreAgg", ag -> ag
                                .topHits(tha -> tha.sort(so -> so
                                                .field(fs -> fs
                                                        .field("hotScore")
                                                        .order(SortOrder.Desc)))
                                        .size(6)))
                );
        SearchRequest searchRequest = builder.build();
        return searchRequest;
    }

    /**
     * 解析Es中的数据并返回
     *
     * @param response
     * @param c3IdAndBaseCategory3
     * @return
     */
    private List<Map<String, Object>> parseChannelData(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> c3IdAndBaseCategory3) {
        List<Map<String, Object>> result = new ArrayList<>();
        LongTermsAggregate aggByCategory3Id = response.aggregations().get("AggByCategory3Id").lterms();
        List<LongTermsBucket> buckets = aggByCategory3Id.buckets().array();
        for (LongTermsBucket bucket : buckets) {
            HashMap<String, Object> map = new HashMap<>();
            long category3Id = bucket.key();
            // 得到子聚合
            Aggregate top6HotScoreAgg = bucket.aggregations().get("top6HotScoreAgg");
            // 将子聚合转为精准的类型
            TopHitsAggregate topHitsAggregate = top6HotScoreAgg.topHits();
            ArrayList<AlbumInfoIndex> albumInfoIndexArrayList = new ArrayList<>();
            for (Hit<JsonData> hit : topHitsAggregate.hits().hits()) {
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndexArrayList.add(albumInfoIndex);
            }
            map.put("baseCategory3", c3IdAndBaseCategory3.get(category3Id)); // 当前的三级分类对象
            map.put("list", albumInfoIndexArrayList); // 当前三级分类下热度值高的6个专辑 集合
            result.add(map);
        }
        return result;
    }


    @SneakyThrows
    @Override
    public void albumOnSale(Long albumId) {
        Long startTime = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(4);
        // 1.创建一个文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        Future<Long> future = threadPoolExecutor.submit(() -> {
            try {
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
                AlbumInfo albumInfoResultData = albumInfoResult.getData();
                if (albumInfoResultData == null) {
                    throw new GuiguException(201, "远程调用获取专辑基本信息失败");
                }
                // 2.给属性赋值
                albumInfoIndex.setId(albumId); // 专辑id
                albumInfoIndex.setAlbumTitle(albumInfoResultData.getAlbumTitle());
                albumInfoIndex.setAlbumIntro(albumInfoResultData.getAlbumIntro());
                albumInfoIndex.setCoverUrl(albumInfoResultData.getCoverUrl());
                albumInfoIndex.setIncludeTrackCount(albumInfoResultData.getIncludeTrackCount());
                albumInfoIndex.setIsFinished(albumInfoResultData.getIsFinished().toString());
                albumInfoIndex.setPayType(albumInfoResultData.getPayType());
                albumInfoIndex.setCreateTime(new Date());
                List<AlbumAttributeValue> albumAttributeValueVoList = albumInfoResultData.getAlbumAttributeValueVoList();
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(vo -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(vo.getAttributeId());
                    attributeValueIndex.setValueId(vo.getValueId());
                    return attributeValueIndex;
                }).toList();
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList); // 专辑的标签
                return albumInfoResultData.getUserId();
            } finally {
                countDownLatch.countDown();
            }
        });

        threadPoolExecutor.submit(() -> {
            try {
                Long userId = future.get();
                Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);// 需要UserId 阻塞
                Assert.notNull(userInfoVoResult, "远程调用获取用户信息失败");
                UserInfoVo userInfo = userInfoVoResult.getData();
                albumInfoIndex.setAnnouncerName(userInfo.getNickname()); // 作者名
            } catch (Exception e) {
                log.error("远程调用获取用户信息失败{}", e.getMessage());
            } finally {
                countDownLatch.countDown();
            }
        });

        threadPoolExecutor.execute(() -> {
            try {
                Result<BaseCategoryView> categoryResult = albumInfoFeignClient.getCategory(albumId);
                BaseCategoryView baseCategoryView = categoryResult.getData();
                Assert.notNull(baseCategoryView, "远程调用获取分类信息失败");
                albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
                albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
                albumInfoIndex.setCategory3Id(baseCategoryView.getCategory3Id());
            } finally {
                countDownLatch.countDown();
            }
        });

        threadPoolExecutor.execute(() -> {
            try {
                Result<AlbumStatVo> albumStatResult = albumInfoFeignClient.getAlbumStat(albumId);
                AlbumStatVo albumStatVo = albumStatResult.getData();
                Assert.notNull(albumStatVo, "远程调用获取专辑统计信息失败");
                albumInfoIndex.setPlayStatNum(albumStatVo.getPlayStatNum());
                albumInfoIndex.setSubscribeStatNum(albumStatVo.getSubscribeStatNum());
                albumInfoIndex.setBuyStatNum(albumStatVo.getBuyStatNum());
                albumInfoIndex.setCommentStatNum(albumStatVo.getCommentStatNum());

                // 3.计算热度 上线在做 这里用随机数
                Double hotScore = new Random().nextDouble();
                albumInfoIndex.setHotScore(hotScore);
            } finally {
                countDownLatch.countDown();
            }
        });

        // 4.存入es
        countDownLatch.await();
        Long endTime = System.currentTimeMillis();
        log.info("专辑文档 {} 存入Es：耗时{}ms", albumId, (endTime - startTime));
        albumIndexRepository.save(albumInfoIndex);

        // 向suggestInfo索引库中保存数据
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(String.valueOf(albumInfoIndex.getId()));
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));

        suggestIndexRepository.save(suggestIndex);
    }

    @Override
    public void albumOffSale(Long albumId) {
        try {
            albumIndexRepository.deleteById(albumId);
            log.info("专辑文档 {} 从Es删除", albumId);
        } catch (Exception e) {
            log.error("专辑文档 {} 从Es删除失败,{}", albumId, e.getMessage());
        }
    }

    @Override
    public void batchAlbumOffSale() {
        try {
            albumIndexRepository.deleteAll();
            log.info("专辑文档 从Es批量删除成功");
        } catch (Exception e) {
            log.error("专辑文档 从Es批量删除失败,{}", e.getMessage());
        }
    }

    @SneakyThrows
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        // 1.创建查询对象
        SearchRequest searchRequest = buildSearchDsl(albumIndexQuery);
        // 2.查询
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        // 3.解析返回结果
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchData(response);

        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        albumSearchResponseVo.setPageNo(pageNo);
        albumSearchResponseVo.setPageSize(pageSize);
        Long total = albumSearchResponseVo.getTotal();
        // 5.计算总页数 总页数 = 总记录数/每页记录数
        Long totalPages = (total % pageSize == 0 ? total / pageSize : total / pageSize + 1);
        albumSearchResponseVo.setTotalPages(totalPages);
        // 4.返回
        return albumSearchResponseVo;
    }

    @SneakyThrows
    @Override
    public Set<String> completeSuggest(String keyword) {
        // 1.创建查询对象
        // 2.构建DSL语句
        SearchRequest searchRequest = buildCompletionDel(keyword);
        // 3.查询
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(searchRequest, SuggestIndex.class);

        // 4.解析返回结果
        Set<String> set = parseCompletionData(response);
        // 5.看看set满不满10条数据
        if (set.size() < 10) {
            // 6.如果小于10条数据，继续补全
            // 7.查询
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient
                    .search(sb -> sb
                                    .index("suggestinfo")
                                    .query(qb -> qb
                                            .match(mqb -> mqb
                                                    .field("title")
                                                    .query(keyword)))
                            , SuggestIndex.class);
            // 8.解析返回结果
            for (Hit<SuggestIndex> hit : searchResponse.hits().hits()) {
                SuggestIndex suggestIndex = hit.source();
                set.add(suggestIndex.getTitle());
                if (set.size() >= 10) {
                    break;
                }
            }
        }
        return set;
    }

    @PostConstruct
    public void initRebuildBloomFilter() {
        // todo 线程工厂
        // 这个定时任务 应该从当天开始算 当天的七天后的凌晨2点执行第一次 且每隔七天 在凌晨两点执行
        ScheduleTaskThreadPoolFactory instance = ScheduleTaskThreadPoolFactory.getInstance();
        Long firstExecTaskTime = ScheduleTaskThreadPoolFactory.diffTime(System.currentTimeMillis());
        instance.execute(new RebuildBloomFilterRunnable(redissonClient, redisTemplate, this), firstExecTaskTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 采用双缓存优化获取专辑信息
     * 一级缓存为Map<Long, Map<String, Object>>, key为专辑id，value为Map<String, Object>
     * 二级缓存在redis中，key为albumInfo:albumId, value为Map<String, Object>
     * 缓存没有命中，则从数据库中查询，并放入缓存中
     *
     * @param albumId
     * @return
     */
    @Override
    public Map<String, Object> getAlbumInfo(Long albumId) {
//        return getDistroCacheAndLockRedissonVersion1(albumId);
//        return getDistroCacheAndLockRedissonVersion2(albumId);
        return getAlbumInfoFromDb(albumId);
    }

    @SneakyThrows
    private Map<String, Object> getDistroCacheAndLockRedissonVersion2(Long albumId) {
        // 定义缓存key
        String cacheKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        // 查询分布式布隆过滤器
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        // 查询缓存
        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.hasText(cacheValue)) {
            return JSON.parseObject(cacheValue, Map.class);
        }
        // 获取分布式锁
        RLock lock = redissonClient.getLock(cacheKey);
        boolean tryLock = lock.tryLock();
        long ttl;
        if (tryLock) {
            // 查询数据库
            try {
                Map<String, Object> albumInfo = itemServiceImpl.getAlbumInfoFromDb(albumId);
                if (albumInfo != null) {
                    ttl = 60 * 60 * 24 * 7L;
                }else {
                    ttl = 60 * 60 * 24L;
                }
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(albumInfo), ttl, TimeUnit.SECONDS);
            } finally {
                lock.unlock();
            }

        }else {
            Thread.sleep(200);
            String result = redisTemplate.opsForValue().get(cacheKey);
            return JSON.parseObject(result, Map.class);
        }
        return null;
    }

    @SneakyThrows
    private Map<String, Object> getDistroCacheAndLockRedissonVersion1(Long albumId) {
        // 定义缓存key
        String cacheKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        // 查询分布式布隆过滤器
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        // 查询缓存
        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.hasText(cacheValue)) {
            return JSON.parseObject(cacheValue, Map.class);
        }
        // 获取分布式锁
        RLock lock = redissonClient.getLock(cacheKey);
        boolean tryLock = lock.tryLock();
        long ttl;
        if (tryLock) {
            // 查询数据库
            try {
                Map<String, Object> albumInfo = itemServiceImpl.getAlbumInfoFromDb(albumId);
                if (albumInfo != null) {
                    ttl = 60 * 60 * 24 * 7L;
                }else {
                    ttl = 60 * 60 * 24L;
                }
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(albumInfo), ttl, TimeUnit.SECONDS);
            } finally {
                lock.unlock();
            }

        }else {
            Thread.sleep(200);
            String result = redisTemplate.opsForValue().get(cacheKey);
            return JSON.parseObject(result, Map.class);
        }
        return null;
    }

    private Map<String, Object> getAlbumInfoFromCache2Version(Long albumId) {
        Map<String, Object> result = new HashMap<>();
        String cacheKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        String cacheValue;
        String loopValue = threadLocal.get();
        boolean loopFlag = StringUtils.hasText(loopValue);
        if (loopFlag) {
            cacheValue = loopValue;
        } else {
            cacheValue = UUID.randomUUID().toString().replaceAll("-", "");// 防止锁误删
        }
        long startTime = System.currentTimeMillis();
        // 1.从一级缓存中获取数据
        boolean b = localCacheMap.containsKey(albumId);
        if (b) {
            log.info("一级缓存命中");
            result = localCacheMap.get(albumId);
        } else {
            // 先看布隆过滤器中是否存在
            boolean b1 = rBloomFilter.contains(albumId);
            if (!b1) {
                log.info("布隆过滤器中未命中");
                return result;
            }
            // 2.从二级缓存中获取数据
            String stringCache2 = redisTemplate.opsForValue().get(RedisConstant.CACHE_INFO_PREFIX + albumId);
            if (StringUtils.hasText(stringCache2)) {
                log.info("二级缓存命中");
                result = JSONObject.parseObject(stringCache2, Map.class);
                localCacheMap.put(albumId, result);
            } else {
                // 3.从数据库中获取数据
                log.info("一级缓存和二级缓存均未命中");
                // 尝试获取分布式锁
                Boolean lock;
                if (!loopFlag) {
                    // 获取锁
                    lock = redisTemplate.opsForValue().setIfAbsent(cacheKey, cacheValue, 30, TimeUnit.SECONDS);
                }// 设置锁的过期时间
                else lock = true; // 已经获取过锁
                if (Boolean.TRUE.equals(lock)) {
                    // 获取锁成功，查询数据库
                    try {
                        long ttl;
                        result = getAlbumInfoFromDb(albumId);
                        if (!result.isEmpty()) {
                            ttl = 60 * 60 * 24 * 7L;
                        } else {
                            ttl = 60 * 60 * 2L;
                        }
                        redisTemplate.opsForValue().set(RedisConstant.CACHE_INFO_PREFIX + albumId, JSONObject.toJSONString(result), ttl, TimeUnit.SECONDS);// 防止了空值固定id值缓存穿透
                        localCacheMap.put(albumId, result);
                    } finally {
                        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                        Long flag = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList(cacheKey), cacheValue);
                        if (flag == 1) { // todo 这里的比对和删除需要整合为原子的操作 使用lua
                            log.info("删除锁成功");
                        } else {
                            log.info("删除锁失败");
                        }
                        threadLocal.remove();
                    }
                } else {
                    // 获取锁失败，等待
                    try {
                        Thread.sleep(200);// 此处需要根据压测结果 精确确定等待时间
                        // 此时理论情况 获取到锁的线程已经查询到数据 并缓存到了Redis中
                        String getRedisCacheAgain = redisTemplate.opsForValue().get(RedisConstant.CACHE_INFO_PREFIX + albumId);
                        if (!StringUtils.hasText(getRedisCacheAgain)) {
                            // todo 极端情况
                            // 获取到锁的线程查询到数据，但是此时其他线程也查询到数据，但是没有缓存到redis中，此时需要从数据库中查询一次
                            while (true) { // 自旋获取锁 获取到则递归查询一次 其他线程自旋从Redis中获取数据
                                String albumInfo = redisTemplate.opsForValue().get(RedisConstant.CACHE_INFO_PREFIX + albumId);
                                if (StringUtils.hasText(albumInfo)) {
                                    result = JSONObject.parseObject(albumInfo, Map.class);
                                    break;
                                }
                                if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(cacheKey, cacheValue, 30, TimeUnit.SECONDS))) {
                                    // 此时获取到锁，则递归查询一次 但注意 此时递归时 不需要再次获取锁 且也不需要再次生成锁标识 否则会有死锁 不能删掉自己的锁了
                                    threadLocal.set(cacheValue);
                                    result = getAlbumInfo(albumId);
                                }
                            }
                        } else {
                            result = JSONObject.parseObject(getRedisCacheAgain, Map.class);
                        }
                    } catch (InterruptedException e) {
                        throw new GuiguException(201, "系统异常");
                    }
                }
            }
        }
        log.info("查询专辑信息耗时:{}", System.currentTimeMillis() - startTime);
        return result;
    }

    private @NotNull Map<String, Object> getAlbumInfoFromDb(Long albumId) {
        //result.put("albumInfo", albumInfo);	              获取专辑信息
        //result.put("albumStatVo", albumStatVo);             获取专辑统计信息
        //result.put("baseCategoryView", baseCategoryView);   获取分类信息
        //result.put("announcer", userInfoVo);	              获取主播信息
        Map<String, Object> map = new HashMap<>();
        // 1.查询专辑信息
        CompletableFuture<Long> albumInfoFuture = CompletableFuture.supplyAsync(() -> {
            log.info("执行查询专辑基本信息的线程是:{}", Thread.currentThread().getName());
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            AlbumInfo albumInfoResultData = albumInfoResult.getData();
            if (albumInfoResultData == null) {
                throw new GuiguException(201, albumId + "专辑不存在");
            }
            map.put("albumInfo", albumInfoResultData);
            return albumInfoResultData.getUserId();
        }, threadPoolExecutor);

        // 2.查询专辑统计信息
        CompletableFuture<Void> albumStatVoFuture = CompletableFuture.runAsync(() -> {
            log.info("执行查询专辑统计信息的线程是:{}", Thread.currentThread().getName());
            Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumStat(albumId);
            AlbumStatVo albumStatVoResultData = albumStatVoResult.getData();
            if (albumStatVoResultData == null) {
                throw new GuiguException(201, albumId + "专辑统计信息不存在");
            }
            map.put("albumStatVo", albumStatVoResultData);
        }, threadPoolExecutor);

        // 3.查询分类信息
        CompletableFuture<Void> albumCategoryViewFuture = CompletableFuture.runAsync(() -> {
            log.info("执行查询专辑分类信息的线程是:{}", Thread.currentThread().getName());
            Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getCategory(albumId);
            BaseCategoryView baseCategoryViewResultData = baseCategoryViewResult.getData();
            if (baseCategoryViewResultData == null) {
                throw new GuiguException(201, albumId + "专辑分类信息不存在");
            }
            map.put("baseCategoryView", baseCategoryViewResultData);
        }, threadPoolExecutor);
        // 4.查询主播信息
        CompletableFuture<Void> userInfoFuture = albumInfoFuture.thenAcceptAsync((userId) -> {
            log.info("执行查询主播信息的线程是:{}", Thread.currentThread().getName());
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
            UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
            if (userInfoVoResultData == null) {
                throw new GuiguException(201, albumId + "专辑的主播信息不存在");
            }
            map.put("announcer", userInfoVoResultData);
        }, threadPoolExecutor);

        CompletableFuture.allOf(albumInfoFuture, albumStatVoFuture, albumCategoryViewFuture, userInfoFuture).join();

        return map;
    }


    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long c1Id, String byType) {
        // 从redis中获取数据
        String albumInfoIndexVoListStr = (String) redisTemplate.opsForHash().get(RedisConstant.RANKING_KEY_PREFIX + c1Id, byType);
        if (!StringUtils.hasText(albumInfoIndexVoListStr)) {
            throw new GuiguException(201, "排行榜数据不存在");
        }
        List<AlbumInfoIndex> albumInfoIndexList = JSONObject.parseArray(albumInfoIndexVoListStr, AlbumInfoIndex.class);
        return albumInfoIndexList.stream().map(albumInfoIndex -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            return albumInfoIndexVo;
        }).toList();
    }

    @Override
    public void preCacheRanking() {

        Result<List<Long>> c1Ids = albumInfoFeignClient.getC1Ids();
        List<Long> c1IdList = c1Ids.getData();
        if (CollectionUtils.isEmpty(c1IdList)) {
            throw new GuiguException(201, "专辑微服务远程查询一级分类Id列表失败");
        }
        // 遍历一级分类Id列表，查询每个一级分类Id对应的专辑数据，并按照五个维度排序，并缓存到Redis中
        String[] byTypes = new String[]{"playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum", "hotScore"};
        c1IdList.forEach(c1Id -> {
            for (String byType : byTypes) {
                try {
                    SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(srb -> srb
                                    .index("albuminfo")
                                    .query(qb -> qb
                                            .term(tqb -> tqb
                                                    .field("category1Id")
                                                    .value(c1Id)))
                                    .sort(sob -> sob.field(fsb -> fsb.field(byType).order(SortOrder.Desc)))
                                    .size(10)
                            , AlbumInfoIndex.class);
                    ArrayList<AlbumInfoIndex> albumInfoIndexes = new ArrayList<>();
                    for (Hit<AlbumInfoIndex> hit : response.hits().hits()) {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        albumInfoIndexes.add(albumInfoIndex);
                    }
                    String jsonString = JSONObject.toJSONString(albumInfoIndexes);
                    redisTemplate.opsForHash().put(RedisConstant.RANKING_KEY_PREFIX + c1Id, byType, jsonString);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public List<Long> findAllAlbumId() {
        Result<List<Long>> result = albumInfoFeignClient.getAlbumIds();
        List<Long> albumIds = result.getData();
        if (CollectionUtils.isEmpty(albumIds))
            throw new GuiguException(201, "专辑微服务远程查询专辑Id列表失败");
        return albumIds;
    }

    @Override
    public void albumStatusUpdate(String orderNo, Long userId) {
        // 根据订单编号查询订单信息
        Result<OrderInfo> orderInfoByOrderNo = orderInfoFeignClient.getOrderInfoByOrderNo(orderNo,userId);
        OrderInfo orderInfoResultData = orderInfoByOrderNo.getData();
        if (orderInfoResultData == null) {
            throw new GuiguException(201, "订单微服务远程查询订单信息失败");
        }
        List<OrderDetail> orderDetailList = orderInfoResultData.getOrderDetailList();
        int size = orderDetailList.size();
        // 获取付款项类型
        String itemType = orderInfoResultData.getItemType();
        Long albumId = null;
        // 如果是vip就直接放回
        if ("1003".equals(itemType)) {
            return;
        }else if("1001".equals(itemType)){
            albumId = orderDetailList.get(0).getItemId();
        }else if("1002".equals(itemType)){
            Long trackId = orderDetailList.get(0).getItemId();
            Result<AlbumInfo> albumInfoByTrackId = albumInfoFeignClient.getAlbumInfoByTrackId(trackId);
            AlbumInfo albumInfoResultData = albumInfoByTrackId.getData();
            if (albumInfoResultData == null) {
                throw new GuiguException(201, "专辑微服务远程查询专辑信息失败");
            }
            albumId = albumInfoResultData.getId();
        }
        assert albumId != null;
        Optional<AlbumInfoIndex> optionalAlbumInfoIndex = albumIndexRepository.findById(albumId);
        optionalAlbumInfoIndex.ifPresent(albumInfoIndex -> {
            Integer buyStatNum = albumInfoIndex.getBuyStatNum();
            albumInfoIndex.setBuyStatNum(buyStatNum + size);
            albumIndexRepository.save(albumInfoIndex);
        });
    }

    /**
     * 构建自动补全查询对象
     *
     * @param keyword
     * @return
     */
    private SearchRequest buildCompletionDel(String keyword) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        Suggester.Builder suggestBuilder = new Suggester.Builder();
        suggestBuilder
                .suggesters("suggestionKeyword", fs -> fs
                        .prefix(keyword).completion(csb -> csb
                                .field("keyword")))
                .suggesters("suggestionKeywordPinyin", fs -> fs
                        .prefix(keyword).completion(csb -> csb
                                .field("keywordPinyin")))
                .suggesters("suggestionKeywordSequence", fs -> fs
                        .prefix(keyword).completion(csb -> csb
                                .field("keywordSequence")));
        SearchRequest searchRequest = builder.index("suggestinfo").suggest(suggestBuilder.build()).build();

        log.info("自动补全查询：{}", searchRequest.toString());

        return searchRequest;
    }

    /**
     * 解析自动补全查询结果
     *
     * @param response
     * @return
     */
    private Set<String> parseCompletionData(SearchResponse<SuggestIndex> response) {
        HashSet<String> set = new HashSet<>();
        Map<String, List<Suggestion<SuggestIndex>>> suggest = response.suggest();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : suggest.entrySet()) {
            List<Suggestion<SuggestIndex>> value = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : value) {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    SuggestIndex suggestIndex = option.source();
                    set.add(suggestIndex.getTitle());
                }
            }

        }
        return set;
    }

    /**
     * 构建查询对象
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildSearchDsl(AlbumIndexQuery albumIndexQuery) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();
        // 1.判断关键字是否携带
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.hasText(keyword)) {
            boolQuery
                    .should(b -> b
                            .match(bq -> bq
                                    .field("albumTitle")
                                    .query(keyword)
                                    .boost(3f)))
                    .should(b -> b
                            .match(bq -> bq
                                    .field("albumIntro")
                                    .query(keyword)
                                    .boost(2f)))
                    .should(b -> b
                            .match(bq -> bq
                                    .field("announcerName")
                                    .query(keyword)
                                    .boost(1f)));
        }
        // 2.判断分类是否携带
        Long category1Id = albumIndexQuery.getCategory1Id();
        Long category2Id = albumIndexQuery.getCategory2Id();
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category1Id != null) {
            boolQuery.must(b -> b.term(bq -> bq.field("category1Id").value(category1Id)));
        }
        if (category2Id != null) {
            boolQuery.must(b -> b.term(bq -> bq.field("category2Id").value(category2Id)));
        }
        if (category3Id != null) {
            boolQuery.must(b -> b.term(bq -> bq.field("category3Id").value(category3Id)));
        }

        // 3.判断标签是否携带 属性（属性id:属性值id）
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attrAndValueId : attributeList) {
                String[] split = attrAndValueId.split(":");
                String attrId = split[0];
                String valueId = split[1];
                boolQuery
                        .must(b -> b
                                .nested(nq -> nq
                                        .path("attributeValueIndexList")
                                        .query(bq -> bq
                                                .bool(bbq -> bbq
                                                        .must(obq -> obq
                                                                .term(tq -> tq
                                                                        .field("attributeValueIndexList.attributeId")
                                                                        .value(attrId)))))));
                boolQuery
                        .must(b -> b
                                .nested(nq -> nq
                                        .path("attributeValueIndexList")
                                        .query(bq -> bq
                                                .bool(bbq -> bbq
                                                        .must(obq -> obq
                                                                .term(tq -> tq
                                                                        .field("attributeValueIndexList.valueId")
                                                                        .value(valueId)))))));
            }
        }
        BoolQuery boolQueryComplete = boolQuery.build();
        Query query = boolQueryComplete._toQuery();
        builder.index("albuminfo")
                .query(query);
        // ================================================基本条件的DSL封装完毕=========================================================
        // 4.判断分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        // 排序（综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序）
        String order = albumIndexQuery.getOrder();
        if (StringUtils.hasText(order)) {
            String[] orderOrigin = order.split(":");
            String num = orderOrigin[0];
            String orderType = orderOrigin[1];
            builder.from((pageNo - 1) * pageSize).size(pageSize);
            switch (num) {
                case "1":
                    builder
                            .sort(sob -> sob
                                    .field(fs -> fs
                                            .field("hotScore")
                                            .order(Objects.equals(orderType, "desc") ? SortOrder.Desc : SortOrder.Asc)));
                    break;
                case "2":
                    builder
                            .sort(sob -> sob
                                    .field(fs -> fs
                                            .field("playStatNum")
                                            .order(Objects.equals(orderType, "desc") ? SortOrder.Desc : SortOrder.Asc)));
                case "3":
                    builder
                            .sort(sob -> sob
                                    .field(fs -> fs
                                            .field("createTime")
                                            .order(Objects.equals(orderType, "desc") ? SortOrder.Desc : SortOrder.Asc)));
            }
        } else {
            // 默认按照热度值降序排序
            builder
                    .sort(sob -> sob
                            .field(fs -> fs
                                    .field("hotScore")
                                    .order(SortOrder.Desc)));
        }
        builder
                .highlight(obh -> obh
                        .fields("albumTitle", hf -> hf
                                .preTags("<font style='color:red'>")
                                .postTags("</font>")));

        SearchRequest searchRequest = builder.build();
        log.info("条件搜索的DSL语句:{}", searchRequest.toString());
        return searchRequest;
    }

    private AlbumSearchResponseVo parseSearchData(SearchResponse<AlbumInfoIndex> response) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        TotalHits totalHits = response.hits().total();
        long total = totalHits.value();
        albumSearchResponseVo.setTotal(total);

        List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
        List<AlbumInfoIndexVo> albumInfoIndexVos = hits.stream().map(hit -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = hit.source();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            // 处理高亮结果
            Map<String, List<String>> highlight = hit.highlight();
            if (!CollectionUtils.isEmpty(highlight)) {
                List<String> albumTitle = highlight.get("albumTitle");
                if (!CollectionUtils.isEmpty(albumTitle)) {
                    albumInfoIndexVo.setAlbumTitle(albumTitle.get(0));
                }
            }
            return albumInfoIndexVo;
        }).toList();

        albumSearchResponseVo.setList(albumInfoIndexVos);
        return albumSearchResponseVo;
    }


}
